package middleware

import (
	"bytes"
	"context"
	"github.com/gin-gonic/gin"
	"io"
	"time"
)

// 利用gin 的 middleware 打印日志
type LogMiddlewareBuilder struct {
	logFn         func(ctx context.Context, l AccessLog) //决定日志级别 决定使用哪个具体的日志实现 决定如何打印日志
	allowReqBody  bool                                   //控制是否打印 req body
	allowRespBody bool                                   //控制是否打印 resp body
}

// 创建一个 log middleware
func NewLogMiddlewareBuilder(logFn func(ctx context.Context, l AccessLog)) *LogMiddlewareBuilder {
	return &LogMiddlewareBuilder{
		logFn: logFn,
	}
}

func (l *LogMiddlewareBuilder) AllowReqBody() *LogMiddlewareBuilder {
	l.allowReqBody = true
	return l // 为了实现链式调用
}

func (l *LogMiddlewareBuilder) AllowRespBody() *LogMiddlewareBuilder {
	l.allowRespBody = true
	return l // 为了实现链式调用
}

// 真正打日志的地方
func (l *LogMiddlewareBuilder) Build() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 获取path
		path := ctx.Request.URL.Path
		if len(path) > 1024 { // 放置攻击者 path很长的攻击
			path = path[:1024]
		}
		method := ctx.Request.Method
		al := AccessLog{
			Path:   path,
			Method: method,
		}
		if l.allowReqBody { // 允许打印 req body
			// Request.Body 是一个 Stream 对象，只能读一次
			body, _ := ctx.GetRawData()
			if len(body) > 2048 { // 进一步控制  req body 大小
				al.ReqBody = string(body[:2048])
			} else {
				al.ReqBody = string(body)
			}
			// 还需要再放回去
			ctx.Request.Body = io.NopCloser(bytes.NewReader(body)) // Request.Body 是一个 Stream 对象，只能读一次  将Request.Body 放回去
			//ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body))
		}

		// 开始请求
		start := time.Now()
		// 处理响应
		if l.allowRespBody { // 如果允许打印resp body    需要构造一个 自定义的responseWriter
			ctx.Writer = &responseWriter{ // ctx.Writer 就是gin.ResponseWrite接口      这里的  responseWriter 实现了 ctx.Writer
				ResponseWriter: ctx.Writer,
				al:             &al,
			}
		}

		// 执行完 ctx.Next 才执行这里 最后执行 打印 resp body
		defer func() { // 打印时间
			al.Duration = time.Since(start)
			//duration := time.Now().Sub(start)
			l.logFn(ctx, al)
		}()

		// 直接执行下一个 middleware...直到业务逻辑
		ctx.Next()
		// 在这里，你就拿到了响应
	}
}

type AccessLog struct {
	Path     string        `json:"path"` //请求路径
	Method   string        `json:"method"`
	ReqBody  string        `json:"req_body"` // 生产不能打  攻击者ke可以构造很大的body
	Status   int           `json:"status"`
	RespBody string        `json:"resp_body"`
	Duration time.Duration `json:"duration"` // 执行时间
}

// 利用装饰器模式装饰gin.ResponseWriter            获取 响应体   自定义一个 responseWriter  间接的获取resp 数据
type responseWriter struct {
	gin.ResponseWriter            // 接口类型 原有接口拿不到响应数据
	al                 *AccessLog // 修改数据 使用指针
}

// 方法重写
func (w *responseWriter) Write(data []byte) (int, error) {
	w.al.RespBody = string(data) // 获取resp body
	return w.ResponseWriter.Write(data)
}

// 方法重写
func (w *responseWriter) WriteHeader(statusCode int) {
	w.al.Status = statusCode
	w.ResponseWriter.WriteHeader(statusCode)
}

/*
gin
type ResponseWriter interface {
	http.ResponseWriter  接口
    x
    y
    z
}

http
type ResponseWriter interface {
      Write([]byte) (int, error)  // 方法重写
      WriteHeader(statusCode int) // 方法重写
}



*/
